#include "p2p/stunserver.h"

#include "base/bytebuffer.h"
#include "base/logging.h"

namespace cricket {

	StunServer::StunServer(base::AsyncUDPSocket* socket) : socket_(socket) {
		socket_->SignalReadPacket.connect(this, &StunServer::OnPacket);
	}

	StunServer::~StunServer() {
		socket_->SignalReadPacket.disconnect(this);
	}

	void StunServer::OnPacket(
		base::AsyncPacketSocket* socket, const char* buf, size_t size,
		const base::SocketAddress& remote_addr) {
			// Parse the STUN message; eat any messages that fail to parse.
			base::ByteBuffer bbuf(buf, size);
			StunMessage msg;
			if (!msg.Read(&bbuf)) {
				return;
			}

			// TODO: If unknown non-optional (<= 0x7fff) attributes are found, send a
			//       420 "Unknown Attribute" response.

			// Send the message to the appropriate handler function.
			switch (msg.type()) {
			case STUN_BINDING_REQUEST:
				OnBindingRequest(&msg, remote_addr);
				break;

			default:
				SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported");
			}
	}

	void StunServer::OnBindingRequest(
		StunMessage* msg, const base::SocketAddress& remote_addr) {
			StunMessage response;
			response.SetType(STUN_BINDING_RESPONSE);
			response.SetTransactionID(msg->transaction_id());

			// Tell the user the address that we received their request from.
			StunAddressAttribute* mapped_addr;
			if (!msg->IsLegacy()) {
				mapped_addr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
			} else {
				mapped_addr = StunAttribute::CreateXorAddress(STUN_ATTR_XOR_MAPPED_ADDRESS);
			}
			mapped_addr->SetAddress(remote_addr);
			response.AddAttribute(mapped_addr);

			SendResponse(response, remote_addr);
	}

	void StunServer::SendErrorResponse(
		const StunMessage& msg, const base::SocketAddress& addr,
		int error_code, const char* error_desc) {
			StunMessage err_msg;
			err_msg.SetType(GetStunErrorResponseType(msg.type()));
			err_msg.SetTransactionID(msg.transaction_id());

			StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
			err_code->SetCode(error_code);
			err_code->SetReason(error_desc);
			err_msg.AddAttribute(err_code);

			SendResponse(err_msg, addr);
	}

	void StunServer::SendResponse(
		const StunMessage& msg, const base::SocketAddress& addr) {
			base::ByteBuffer buf;
			msg.Write(&buf);
			if (socket_->SendTo(buf.Data(), buf.Length(), addr) < 0)
				LOG_ERR(LS_ERROR) << "sendto";
	}

}  // namespace cricket
